package org.jiuling.poi.tools;

import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.jiuling.local.tools.create.IntegerCreate;
import org.jiuling.local.tools.verify.*;
import org.jiuling.poi.tools.annotations.Excel;
import org.jiuling.poi.tools.enums.ListDataType;
import org.jiuling.poi.tools.exception.PioToolsException;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.*;

/**
 * 表格导出工具
 * @author 紫灵
 * @date 2022年05月16日 22时33分28秒
 */
public class ExcelExport {

    /**
     * 通过列表导出
     * @param title 标题
     * @param sheetName 工作表名称
     * @param headers 表头数组
     * @param dataList 数据列表
     * @param listDataType 列表数据类型
     * @param outputStream 输出流
     * @return 输出流
     */
    public static OutputStream exportByList(String title, String sheetName, String[] headers, List<List<Object>> dataList, ListDataType listDataType, OutputStream outputStream) throws IOException, PioToolsException {
        if(StringVerify.isBlank(sheetName)){
            throw new PioToolsException("工作表名称不能为空！");
        }
        XSSFWorkbook workbook = new XSSFWorkbook();
        // 创建工作表
        XSSFSheet sheet = workbook.createSheet(sheetName);

        int lineNum = 0;
        // 如果数据不为空(行数据)
        if(!ListVerify.isEmpty(dataList)){
            switch (listDataType){
                case ROW_DATA:
                    // 行号
                    lineNum = setTitleAndHeader(title,headers,null,null,sheet,dataList.get(0).size());
                    for (List<Object> data : dataList){
                        // 创建行
                        XSSFRow dataRow = sheet.createRow(lineNum);
                        for (int i = 0; i < data.size(); i++) {
                        // 创建列并设置数据
                        dataRow.createCell(i).setCellValue(String.valueOf(data.get(i)));
                        }
                        lineNum++;
                    }
                    break;
                case COLUME_DATA:
                    // 行号
                    lineNum = setTitleAndHeader(title,headers,null,null,sheet,dataList.size());
                    // 根据数据长度创建行
                    for (int i = 0;i < dataList.get(0).size(); i++) {
                        XSSFRow dataRow = sheet.createRow(lineNum+i);
                        for (int j = 0; j < dataList.size(); j++) {
                            dataRow.createCell(j).setCellValue(String.valueOf(dataList.get(j).get(i)));
                        }
                        lineNum++;
                    }
                    break;
                default:
                    throw new PioToolsException("列表数据类型设置不正确！");
            }
        }
        workbook.write(outputStream);
        return outputStream;
    }

    /**
     * 通过Map集合导出
     * @param title 标题
     * @param sheetName 工作表名称
     * @param headerMap 表头Map列表
     * @param dataMapList 数据Map集合列表
     * @param outputStream 输出流
     * @return 输出路
     */
    public static OutputStream exportByMap(String title, String sheetName, Map<String,Object> headerMap, List<Map<String,Object>> dataMapList, OutputStream outputStream) throws IOException, PioToolsException {
        return exportByMap(title,sheetName,null,headerMap,dataMapList,outputStream);
    }

    /**
     * 通过Map集合导出
     * @param title 标题
     * @param sheetName 工作表名称
     * @param headers 表头数组
     * @param dataMapList 数据Map集合列表
     * @param outputStream 输出流
     * @return 输出路
     */
    public static OutputStream exportByMap(String title, String sheetName, String[] headers, List<Map<String,Object>> dataMapList, OutputStream outputStream) throws IOException, PioToolsException {
        return exportByMap(title,sheetName,headers,null,dataMapList,outputStream);
    }

    /**
     * 通过Map集合导出
     * @param title 标题
     * @param sheetName 工作表名称
     * @param headers 表头数组
     * @param headerMap 表头Map列表
     * @param dataMapList 数据Map集合列表
     * @param outputStream 输出流
     * @return 输出路
     */
    private static OutputStream exportByMap(String title, String sheetName,String[] headers,Map<String,Object> headerMap, List<Map<String,Object>> dataMapList, OutputStream outputStream) throws PioToolsException, IOException {
        if(StringVerify.isBlank(sheetName)){
            throw new PioToolsException("工作表名称不能为空！");
        }
        XSSFWorkbook workbook = new XSSFWorkbook();
        // 创建工作表
        XSSFSheet sheet = workbook.createSheet(sheetName);
        int lineNum = 0;
        // 排序集合
        Map<String,Integer> sortMap = new HashMap<>();
        // 行号
        lineNum = setTitleAndHeader(title, headers,headerMap,sortMap, sheet, dataMapList.get(0).size());

        if(!ListVerify.isEmpty(dataMapList)){
            // 遍历数据
            for (Map<String, Object> dataMap : dataMapList) {
                lineNum = setDataByMap(lineNum,sheet,dataMap,sortMap);
            }
        }
        workbook.write(outputStream);
        return outputStream;
    }

    public static <T> OutputStream exportByObject(String sheetName,List<T> objectList,OutputStream outputStream) throws PioToolsException, IOException, IllegalAccessException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException {

        if(StringVerify.isBlank(sheetName)){
            throw new PioToolsException("工作表名称不能为空！");
        }

        XSSFWorkbook workbook = new XSSFWorkbook();
        // 创建工作表
        XSSFSheet sheet = workbook.createSheet(sheetName);

        // 获取列表对象所属类
        Class clazz = objectList.get(0).getClass();
        String title = null;
        if(AnnotationVerify.includeAnnotation(clazz.getAnnotations(),Excel.class,false)){
            Excel excel = (Excel) clazz.getDeclaredAnnotation(Excel.class);
            title = excel.value();
        }else {
            // 获取类名作为标题 TODO 如果存在注解则使用注解
            title = clazz.getCanonicalName().replace(clazz.getPackageName() + ".", "");
        }
        // 获取类属性列表 TODO 如果存在注解则按照注解规则取值
        Field[] fields = clazz.getDeclaredFields();
        int columeNum = fields.length;
        Map<String,Object> headerMap = new HashMap<>();
        // 排序集合
        Map<String,Integer> sortMap = new HashMap<>();
        for (Field field : fields) {
            if(AnnotationVerify.includeAnnotation(field.getAnnotations(),Excel.class,false)){
                headerMap.put(field.getName(),field.getDeclaredAnnotation(Excel.class).value());
            }else{
                // 设置表头 TODO 如果存在注解则按照注解取值 如果租注解中存在排序则进行排序
                headerMap.put(field.getName(),field.getName());
            }
        }
        int lineNum = setTitleAndHeader(title,null,headerMap,sortMap,sheet,columeNum);

        for (T object : objectList) {
            // 创建行
            XSSFRow dataRow = sheet.createRow(lineNum);
            for (Field field : fields) {
                String fieldName = field.getName();
                field.setAccessible(true);
                Object v = field.get(object);
                if(!ObjectVerify.isNull(v)) {
                    // 如果v是基础类型则直接填充数据
                    if (ObjectVerify.isBaseType(v)) {
                        if(v instanceof Boolean){
                            Boolean nv = (Boolean)v;
                            if(nv){
                                v = "是";
                            }else {
                                v = "否";
                            }
                        }
                        dataRow.createCell(sortMap.get(fieldName)).setCellValue(String.valueOf(v));
                    }else {
                        Excel excel = field.getDeclaredAnnotation(Excel.class);
//                        String valueFieldName = field.getDeclaredAnnotation(Excel.class).field();
                        Class<?> aClass = v.getClass();
                        Object v1 = "";
                        if(aClass.isEnum()){
                            for (Object enumConstant : aClass.getEnumConstants()) {
                                Object getCode = aClass.getMethod(excel.getValue()).invoke(enumConstant);
                                if(String.valueOf(getCode).equals(String.valueOf(v))){
                                    v1 = aClass.getMethod(excel.valueName()).invoke(enumConstant);
                                    break;
                                }
                            }
                        }else {
                            v1 = v.getClass().getMethod(excel.field()).invoke(v);
                        }

                        dataRow.createCell(sortMap.get(fieldName)).setCellValue(String.valueOf(v1));
                    }
                }
            }
            lineNum++;
        }

        workbook.write(outputStream);
        return outputStream;
    }



    /**
     * 设置标题和表头
     * @param title 标题
     * @param headers 表头
     * @param sheet 当前工作表
     * @param dataLen 数据长度
     * @return 开始行号
     */
    private static int setTitleAndHeader(String title,String[] headers,Map<String,Object> headerMap,Map<String,Integer> sortMap,XSSFSheet sheet,int dataLen) throws PioToolsException {
        int lineNum = 0;
        // 工作表名称不为空则设置工作表名称
        if(!StringVerify.isBlank(title)){
            lineNum = setTitle(lineNum,sheet,title,dataLen);
        }
        int headersLen;
        // 如果表头数组不为空则设置表头
        if(!ArrayVerify.isNull(headers)){
            headersLen = headers.length;
            if(headersLen != dataLen){
                throw new PioToolsException("表头列("+headersLen+")与数据列("+dataLen+")长度不一致");
            }
            lineNum = setHeader(lineNum,sheet,headers);
        }else if(!MapVerify.isNull(headerMap)){
            if(MapVerify.isNull(sortMap)){
                throw new PioToolsException("当表头集合存在时排序集合不能为空！");
            }
            headersLen = headerMap.size();
            if(headersLen != dataLen){
                throw new PioToolsException("表头列("+headersLen+")与数据列("+dataLen+")长度不一致");
            }
            lineNum = setHeader(lineNum,sheet,headerMap,sortMap);
        }
        return lineNum;
    }

    /**
     * 设置标题
     * @param lineNum 行号
     * @param sheet 工作表
     * @param title 标题
     * @param datalength 数据长度
     * @return 行号
     */
    private static int setTitle(int lineNum,XSSFSheet sheet,String title,int datalength){
        // 创建行
        XSSFRow row = sheet.createRow(lineNum);
        // 创建列并设置标题
        row.createCell(0).setCellValue(title);
        // 合并单元格
        CellRangeAddress cellRangeAddress = new CellRangeAddress(0, 0, 0, datalength-1);
        sheet.addMergedRegion(cellRangeAddress);
        return lineNum+1;
    }

    /**
     * 设置表头
     * @param lineNum 行号
     * @param sheet 工作表
     * @param headers 表头数组
     * @return 行号
     */
    private static int setHeader(int lineNum,XSSFSheet sheet,String[] headers){
        // 创建行
        XSSFRow row = sheet.createRow(lineNum);
        // 设置表头
        for (int i = 0; i < headers.length; i++) {
            // 设置表头的值
            row.createCell(i).setCellValue(headers[i]);
        }
        return lineNum+1;
    }

    /**
     * 设置表头
     * @param lineNum 当前行号
     * @param sheet 工作表
     * @param headerMap 表头集合
     * @param sortMap 排序集合
     * @return 行号
     */
    private static int setHeader(int lineNum,XSSFSheet sheet,Map<String,Object> headerMap,Map<String,Integer> sortMap) throws PioToolsException {
        return setDataByMap(lineNum,sheet,headerMap,sortMap);
    }

    /**
     * 设置Map数据
     * @param lineNum 当前行号
     * @param sheet 工作表
     * @param dataMap 数据集合
     * @param sortMap 排序集合
     * @return 行号
     */
    private static int setDataByMap(int lineNum,XSSFSheet sheet,Map<String,Object> dataMap,Map<String,Integer> sortMap) throws PioToolsException {
        // 获取被占用列号
        Set<Integer> columeNumSet = new HashSet<>();
        // 最大行数
        int maxcolumeNum = 0;
        if(!MapVerify.isBlank(sortMap)) {
            for (Map.Entry<String,Integer> sort : sortMap.entrySet()){
                Integer v = sort.getValue();
                // 获取排序集合中最大行数
                if(v > maxcolumeNum){
                    maxcolumeNum = v;
                }
                if(columeNumSet.contains(v)){
                    throw new PioToolsException("第"+v+"列数据重复！");
                }
                columeNumSet.add(v);
            }
        }
        Map<String,Object> unsortedFieldMap = new HashMap();
        // 判断列长度是否超过最大数据长度（列从0开始，数据长度相应-1）
        if(maxcolumeNum > dataMap.size()-1){
            throw new PioToolsException("排序最大列长度不能超过数据列长度！");
        }
        // 创建行
        XSSFRow dataRow = sheet.createRow(lineNum);
        int columeNum = 0;
        for (Map.Entry<String,Object> data : dataMap.entrySet()){
            String key = data.getKey();
            // 获取当前列号
            Integer currentColumeNum = sortMap.get(key);

            // 如果列号为空则设置当前列号
            if(IntegerVerify.isNull(currentColumeNum)){
                // 判断列好是否存在
                if(columeNumSet.contains(columeNum)){
                    unsortedFieldMap.put(key,data.getValue());
                    continue;
                }
                columeNumSet.add(columeNum);
                currentColumeNum = columeNum;
                sortMap.put(key,currentColumeNum);
            }
            columeNum++;
            dataRow.createCell(currentColumeNum).setCellValue(String.valueOf(data.getValue()));
        }
        if(!MapVerify.isBlank(unsortedFieldMap)){
            unsortedFieldMap.forEach((k,v) ->{
                int newColumeNum = IntegerCreate.replenishAndAutoIncrement(columeNumSet);
                columeNumSet.add(newColumeNum);
                sortMap.put(k,newColumeNum);
                dataRow.createCell(newColumeNum).setCellValue(String.valueOf(v));
            });
        }
        return lineNum+1;
    }

    public static void main(String[] args) throws IOException, PioToolsException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException {
        // 创建文件
        FileOutputStream fileOutputStream = new FileOutputStream("F:\\1.xlsx");
        List<User> userList = new ArrayList<>();
        User user = new User();
        user.setId(1);
        user.setName("紫灵1");
        user.setTel(18981989890L);
        user.setEmail("547683425@qq.com");
        userList.add(user);
        User user2 = new User();
        user2.setId(2);
        user2.setName("紫灵2");
        user2.setTel(13438990245L);
        user2.setEmail("308870162@qq.com");
        userList.add(user2);
        OutputStream outputStream = exportByObject("工作表1",userList, fileOutputStream);
        outputStream.flush();
    }

    @Excel("用户表")
    static class User{
        @Excel("用户id")
        private int id;
        @Excel("用户名")
        private String name;
        @Excel("手机号")
        private Long tel;
        @Excel("电子邮件")
        private String email;

        public int getId() {
            return id;
        }

        public void setId(int id) {
            this.id = id;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public Long getTel() {
            return tel;
        }

        public void setTel(Long tel) {
            this.tel = tel;
        }

        public String getEmail() {
            return email;
        }

        public void setEmail(String email) {
            this.email = email;
        }
    }

}
